﻿using System;
using System.Diagnostics;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DiscoveryLogic.UI.Charts
{
    public class AxisLabel
    {
        #region fields

        private double _Angle;
        private Point _Position;
        private string _Text;
        private double _ActualLeft;
        private double _ActualTop;
        private double _ActualWidth;
        private double _ActualHeight;
        private double _ActualTextHeight;
        private double _ActualTextWidth;
        #endregion

        #region props
        internal double Angle
        {
            get
            {
                return _Angle;
            }
            set
            {
                if (value >= -90 && value <= 90)
                    _Angle = value;
                else if (double.IsNaN(value))
                    _Angle = 0;
                else 
                    Debug.Assert(false,"Label angle must be between -90 and 90");
            }
        }

        internal Point Position
        {
            get
            {
                return _Position;
            }
            set
            {
                _Position = value;
            }
        }

        internal string Text
        {
            get
            {
                return _Text;
            }
            set
            {
                _Text = value;
            }
        }

        internal PlacementTypes Placement
        {
            get; set;
        }

        internal Canvas Visual { get; set; }

        internal double ActualTop
        {
            get
            {
                return _ActualTop;
            }
            set
            {
                _ActualTop = value;
            }
        }

        internal double ActualLeft
        {
            get
            {
                return _ActualLeft;
            }
            set
            {
                _ActualLeft = value;
            }
        }

        internal double ActualWidth
        {
            get
            {
                return _ActualWidth;
            }
            set
            {
                _ActualWidth = value;
            }
        }

        internal double ActualHeight
        {
            get
            {
                return _ActualHeight;
            }
            set
            {
                _ActualHeight = value;
            }
        }

        internal double ActualTextWidth
        {
            get
            {
                return _ActualTextWidth;
            }
            set
            {
                _ActualTextWidth = value;
            }
        }

        internal double ActualTextHeight
        {
            get
            {
                return _ActualTextHeight;
            }
            set
            {
                _ActualTextHeight = value;
            }
        }

        internal double FontSize { get; set; }
        internal FontFamily FontFamily { get; set; }
        internal Brush FontColor { get; set; }
        internal FontStyle FontStyle { get; set; }
        internal FontWeight FontWeight { get; set; }

        private TextBlock TextElement
        {
            get; set;
        }
        private RotateTransform Rotation { get; set; }
        #endregion

        #region public/internal methods
        internal void ApplyProperties(AxisLabel axisLabel)
        {
            TextElement.Text = axisLabel.Text;
            TextElement.Foreground = FontColor;
            TextElement.FontSize = FontSize;
            TextElement.FontFamily = FontFamily;
            TextElement.FontStyle = FontStyle;
            TextElement.FontWeight = FontWeight;
        }

        internal void CreateVisualObject()
        {
            Visual = new Canvas();
            TextElement=new TextBlock();
            Rotation=new RotateTransform();
            TextElement.RenderTransform = Rotation;
            Visual.Children.Add(TextElement);

            ApplyProperties(this);
            SetPosition();
        }
        #endregion

        #region private methods
        private void SetPosition()
        {
            ActualTextHeight = TextElement.ActualHeight;
            ActualTextWidth = TextElement.ActualWidth;
            switch(Placement)
            {
                case PlacementTypes.Top:
                    SetPositionTop(Position);
                    break;
                case PlacementTypes.Bottom:
                    SetPositionBottom(Position);
                    break;
                case PlacementTypes.Left:
                    SetPositionLeft(Position);
                    break;
                case PlacementTypes.Right:
                    SetPositionRight(Position);
                    break;
            }
            CalculateSize(GetRadians(Angle));
        }

        private void SetPositionTop(Point newPos)
        {
            // Stores the top and left of the textblock
            Double top;
            Double left;

            // Gets the positive value of the angle
            Double angle = GetAngle(Angle);

            // if the angle is zero the do not apply rotation logic just get the horizontal center and place it
            // using absolute value
            if (angle == 0)
            {
                // Get the horizontal center
                left = ActualTextWidth / 2;

                // set the top same as the text height
                top = ActualTextHeight;

                // set rotation to zero
                Rotation.Angle = 0;

                // set the top and left for the AxisLabel element
                Visual.SetValue(Canvas.LeftProperty, newPos.X - left);
                Visual.SetValue(Canvas.TopProperty, newPos.Y - top);

                // set the actual Top, left same as the visual top and left
                ActualTop = (Double)Visual.GetValue(Canvas.TopProperty);
                ActualLeft = (Double)Visual.GetValue(Canvas.LeftProperty);
            }
            else if (angle > 90)
            {
                // this is same as placing the axis label to the right
                SetPositionRight(newPos);
            }
            else
            {
                // this is same as placing the axis label to the left
                SetPositionLeft(newPos);
            }
        }

        private void SetPositionBottom(Point newPos)
        {
            // Stores the top and left of the textblock
            Double top;
            Double left;

            // Gets the positive value of the angle
            Double angle = GetAngle(Angle);

            // if the angle is zero then do not apply rotation logic just get the horizontal center and place it
            // using absolute value
            if (angle == 0)
            {
                // Get the horizontal center
                left = ActualTextWidth / 2;

                // set the top to zero
                top = 0;

                // set rotation to zero
                Rotation.Angle = 0;

                // set the top and left for the AxisLabel element
                Visual.SetValue(Canvas.LeftProperty, newPos.X - left);
                Visual.SetValue(Canvas.TopProperty, newPos.Y + top);

                // set the actual Top, left same as the visual top and left
                ActualTop = (Double)Visual.GetValue(Canvas.TopProperty);
                ActualLeft = (Double)Visual.GetValue(Canvas.LeftProperty);
            }
            else if (angle > 90)
            {
                // this is same as placing the axis label to the left
                SetPositionLeft(newPos);
            }
            else
            {
                // this is same as placing the axis label to the right
                SetPositionRight(newPos);
            }
        }

        private void SetPositionLeft(Point newPos)
        {
            // Stores the top and left of the textblock
            Double top;
            Double left;

            // Get the angle in radians
            Double radians = GetRadians(Angle);

            // The angle made by the diagonal (passing from the top left corner to center right) with the horizontal
            Double relativeAngle = Math.Atan(ActualTextHeight / (2 * ActualTextWidth));

            // length of the diagonal (passing from the top left corner to center right)
            Double length = Math.Sqrt(Math.Pow(ActualTextHeight / 2, 2) + Math.Pow(ActualTextWidth, 2));

            // Gets the positive value of the angle
            Double angle = GetAngle(Angle);

            // Set the transform angle
            Rotation.Angle = angle;

            // Set the transform position
            Rotation.CenterX = 0;
            Rotation.CenterY = 0.5;

            // calculate the top and left for the TextBlock
            left = length * Math.Cos(radians + relativeAngle);
            top = length * Math.Sin(radians + relativeAngle);

            // set the top and left for the AxisLabel element
            Visual.SetValue(Canvas.LeftProperty, newPos.X - left);
            Visual.SetValue(Canvas.TopProperty, newPos.Y - top);

            if (angle > 90)
            {
                // If the angle is > 90 this means the title will be slanting upwards towards left of the point specified in position
                // Hence the actual left of the axis label element does not change
                ActualTop = (Double)Visual.GetValue(Canvas.TopProperty) + top - ((ActualTextHeight / 2) * Math.Sin(GetRadians(90 - angle)));
                ActualLeft = (Double)Visual.GetValue(Canvas.LeftProperty);
            }
            else
            {
                // If the angle is <= 90 this means the title will be slanting downwards towards left of the point specified in position
                // Hence the actual top of the axis label element does not change
                ActualTop = (Double)Visual.GetValue(Canvas.TopProperty);
                ActualLeft = (Double)Visual.GetValue(Canvas.LeftProperty) + left - ((ActualTextHeight / 2) * Math.Cos(GetRadians(90 - Angle))); ;
            }
        }

        private void SetPositionRight(Point newPos)
        {
            double top;
            double left;
            double relativeAngle = Math.Atan(ActualTextHeight/(2*ActualTextWidth));
            double length = Math.Sqrt(Math.Pow(ActualTextHeight/2, 2) + Math.Pow(ActualTextWidth, 2));
            double angle = GetAngle(Angle);
            Rotation.CenterX = 0;
            Rotation.CenterY = 0.5;
            top = (ActualTextHeight/2)*Math.Cos(GetRadians(angle));
            left = -(ActualTextHeight/2)*Math.Sin(GetRadians(angle));
            Visual.SetValue(Canvas.LeftProperty,newPos.X -left);
            Visual.SetValue(Canvas.TopProperty,newPos.Y - top);
            if(angle>90)
            {
                ActualTop = (double) Visual.GetValue(Canvas.TopProperty) - top +
                            length*Math.Sin(GetRadians(angle) + relativeAngle);
                ActualLeft = (double) Visual.GetValue(Canvas.LeftProperty);
            }
            else
            {
                ActualTop = (double) Visual.GetValue(Canvas.TopProperty);
                ActualLeft = (double) Visual.GetValue(Canvas.LeftProperty) + left -
                             (ActualTextHeight/2)*Math.Cos(GetRadians(90 - angle));
            }
        }

        internal static double GetAngle(double angle)
        {
            return angle >= 0 ? angle : 360 + angle;
        }

        internal static double GetRadians(double angle)
        {
            return Math.PI/180*GetAngle(angle);
        }

        private void CalculateSize(Double radianAngle)
        {
            // length of the diagonal from top left to bottom right
            Double length = Math.Sqrt(Math.Pow(ActualTextHeight, 2) + Math.Pow(ActualTextWidth, 2));

            // angle made by the diagonal with respect to the horizontal
            Double beta = Math.Atan(ActualTextHeight / ActualTextWidth);

            // calculate the two possible height and width values using the diagonal length and angle
            Double height1 = length * Math.Sin(radianAngle + beta);
            Double height2 = length * Math.Sin(radianAngle - beta);
            Double width1 = length * Math.Cos(radianAngle + beta);
            Double width2 = length * Math.Cos(radianAngle - beta);

            // Actual height will be the maximum of the two calculated heights
            ActualHeight = Math.Max(Math.Abs(height1), Math.Abs(height2));

            // Actual width will be the maximum of the two calculated widths
            ActualWidth = Math.Max(Math.Abs(width1), Math.Abs(width2));
        }
        #endregion
    }
}
